Проект по А/B-тестированию¶

Ваша задача — провести оценку результатов A/B-теста.

В вашем распоряжении есть датасет с действиями пользователей, техническое задание и несколько вспомогательных датасетов.

  • Оцените корректность проведения теста и проанализируйте его результаты.

    Чтобы оценить корректность проведения теста:

  • удостоверьтесь, что нет пересечений с конкурирующим тестом и нет пользователей, участвующих в двух группах теста одновременно;

  • проверьте равномерность распределения пользователей по тестовым группам и правильность их формирования

Техническое задание

  • Название теста: recommender_system_test ; Группы: А (контрольная), B (новая платёжная воронка);
  • Дата запуска: 2020-12-07;
  • Дата остановки набора новых пользователей: 2020-12-21;
  • Дата остановки: 2021-01-04;
  • Ожидаемое количество участников теста: 15% новых пользователей из региона EU;
  • Назначение теста: тестирование изменений, связанных с внедрением улучшенной рекомендательной системы;
  • Ожидаемый эффект: за 14 дней с момента регистрации в системе пользователи покажут улучшение каждой метрики не менее, чем на 5 процентных пунктов:

    • конверсии в просмотр карточек товаров — событие product_page
    • просмотры корзины — product_cart
    • покупки — purchase .

    Загрузите данные теста, проверьте корректность его проведения и проанализируйте полученные результаты

Данные

data.csv

data.csv

data.csv

data.csv

Структура файлa

  • /datasets/data.csv — календарь маркетинговых событий на 2020 год;
  • name — название маркетингового события;
  • regions — регионы, в которых будет проводиться рекламная кампания;
  • start_dt — дата начала кампании;
  • finish_dt — дата завершения кампании.

    Структура файла:

  • /datasets/data.csv — все пользователи, зарегистрировавшиеся в интернет-магазине в период с 7 по 21 декабря 2020 года;
  • user_id — идентификатор пользователя;
  • first_date — дата регистрации;
  • region — регион пользователя;
  • device — устройство, с которого происходила регистрация.

    Структура файла:

  • /datasets/data.csv — все события новых пользователей в период с 7 декабря 2020 по 4 января 2021 года;
  • user_id — идентификатор пользователя;
  • event_dt — дата и время события;
  • event_name — тип события;
  • details — дополнительные данные о событии. Например, для покупок, purchase , в этом поле хранится стоимость покупки в долларах.

    Структура файла:

  • /datasets/data.csv — таблица участников тестов.
    • user_id — идентификатор пользователя;
    • ab_test — название теста;
    • group — группа пользователя

План выполнения задания:

  1. Подготовьте данные, оцените их целостность:
    • Требуется ли преобразование типов?
    • Присутствуют ли пропущенные значения и дубликаты? Если да, то какова их природа?
  2. Оцените корректность проведения теста:

    • Выделите пользователей участвующих в тесте и проверьте:

      • период набора пользователей в тест и его соответствие требованиям технического задания;
      • регион регистрации пользователей: все ли попавшие в тест пользователи представляют целевой регион и составляет ли общее количество пользователей из целевого региона 15% от общего числа пользователей из целевого региона, зарегистрированных в период набора пользователей в тест;
      • динамику набора пользователей в группы теста и проверьте равномерность распределения пользователей по группам теста и корректность их формирования;
        • (опционально) оцените недельную цикличность набора пользователей в группы
    • Удостоверьтесь, что нет пересечений с конкурирующим тестом и нет пользователей, участвующих в двух группах теста одновременно.
  • Изучите данные о пользовательской активности:
    • даты совершения событий участниками теста: совпадают ли они с датами проведения теста, согласно техническому заданию;
    • активность пользователей: все ли зарегистрированные пользователи прошли авторизацию и совершали переход по продуктовой воронке; если есть пользователи, которые не совершали событий после регистрации, изучите их количество и распределение между группами теста; сделайте вывод о необходимости учитывать пользователей без событий при изучении результатов теста;
    • горизонт анализа: рассчитайте лайфтайм совершения события пользователем после регистрации, оставьте только те события, которые были совершены в первые 14 дней с момента регистрации; проверьте, что все участники теста имели возможность совершать события все 14 дней с момента регистрации, оцените когда пользователи совершают свои первые события каждого вида.
    • Представьте развернутый вывод о соответствии теста требованиям технического задания и возможности получения достоверных результатов АБ-теста, исходя из базового показателя конверсии в 50%.
  1. Проведите исследовательский анализ данных:
  • Распределение количества событий на пользователя в разрезе групп теста: постройте гистограмму распределения этой величины в разрезе групп и сравните её средние значения между собой у групп теста;
  • Динамика количества событий в группах теста по дням: изучите распределение числа событий по дням и сравните динамику групп теста между собой.
  • Убедитесь, что время проведения теста не совпадает с маркетинговыми и другими активностями. Настройте автоматическую проверку, выдающую список событий, пересекающихся с тестом. При необходимости оцените воздействие маркетинговых событий на динамику количества событий.
  • Продуктовая воронка: постройте простые продуктовые воронки для двух групп теста с учетом логической последовательности совершения событий; изучите изменение конверсии в продуктовой воронке тестовой группы, по сравнению с контрольной: наблюдается ли ожидаемый эффект увеличения конверсии в группе В, относительно конверсии в группе А?
  • Сделайте общий вывод об изменении пользовательской активности в тестовой группе, по сравнению с контрольной.
  1. Проведите оценку результатов A/B-тестирования:
  • Проверьте статистическую разницу долей z-критерием.
  • Что можно сказать про результаты A/B-тестирования? Был ли достигнут ожидаемый эффект в изменении конверсии?
  1. Опишите выводы по этапу исследовательского анализа данных и по проведённой оценке результатов A/B-тестирования. Сделайте общее заключение о корректности проведения теста. Дайте рекомендации

Загрузка данных¶

In [1]:
import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
import matplotlib.pyplot as plt
from pandas.plotting import register_matplotlib_converters
import seaborn as sns
import plotly.express as px
from plotly import graph_objects as go
import datetime as dt
from datetime import timedelta
import warnings
import scipy.stats as stats
from scipy.stats import mannwhitneyu
import os
from statsmodels.stats.proportion import proportions_ztest
In [2]:
filename = "ab_project_marketing_events.csv"
filename1 = "final_ab_new_users.csv"
filename2 = "final_ab_events.csv"
filename3 = "final_ab_participants.csv"
downloads_folder = "C:\\Users\\123s\\Downloads\\"

df_ab = pd.read_csv(downloads_folder + filename)
df_user = pd.read_csv(downloads_folder + filename1)
df_event = pd.read_csv(downloads_folder + filename2)
df_participant = pd.read_csv(downloads_folder + filename3)

#df_ab = pd.read_csv('/datasets/ab_project_marketing_events.csv')
#df_user = pd.read_csv('/datasets/final_ab_new_users.csv')
#df_event = pd.read_csv('/datasets/final_ab_events.csv')
#df_participant = pd.read_csv('/datasets/final_ab_participants.csv')

Предобработка¶

In [3]:
def df_viewing(df):
    display(df.head())
    print('---------------------------------------------------------------------------------------------------------')
    print(df.info())
    print('---------------------------------------------------------------------------------------------------------')
    print(df.describe())
    print('---------------------------------------------------------------------------------------------------------')
    print(df.isna().sum())
    print('---------------------------------------------------------------------------------------------------------')
    print(df.duplicated().sum())
In [4]:
df_viewing(df_ab)
name regions start_dt finish_dt
0 Christmas&New Year Promo EU, N.America 2020-12-25 2021-01-03
1 St. Valentine's Day Giveaway EU, CIS, APAC, N.America 2020-02-14 2020-02-16
2 St. Patric's Day Promo EU, N.America 2020-03-17 2020-03-19
3 Easter Promo EU, CIS, APAC, N.America 2020-04-12 2020-04-19
4 4th of July Promo N.America 2020-07-04 2020-07-11
---------------------------------------------------------------------------------------------------------
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 14 entries, 0 to 13
Data columns (total 4 columns):
 #   Column     Non-Null Count  Dtype 
---  ------     --------------  ----- 
 0   name       14 non-null     object
 1   regions    14 non-null     object
 2   start_dt   14 non-null     object
 3   finish_dt  14 non-null     object
dtypes: object(4)
memory usage: 576.0+ bytes
None
---------------------------------------------------------------------------------------------------------
                            name regions    start_dt   finish_dt
count                         14      14          14          14
unique                        14       6          14          14
top     Christmas&New Year Promo    APAC  2020-12-25  2021-01-03
freq                           1       4           1           1
---------------------------------------------------------------------------------------------------------
name         0
regions      0
start_dt     0
finish_dt    0
dtype: int64
---------------------------------------------------------------------------------------------------------
0

Датасет ,df_ab- календарь маркетинговых событий на 2020 год. Содержит 14 маркетинговых событий, в 6 регионах. Пропусков и явных дубликатов нет. Требуется преобразование столбов с датой в datetime и смнена названия столбца с name, regions

In [5]:
df_ab.columns = ['event_name','region','start_dt','finish_dt']
In [6]:
df_ab['start_dt'] = pd.to_datetime(df_ab['start_dt'])
df_ab['finish_dt'] = pd.to_datetime(df_ab['finish_dt'])
In [7]:
df_ab.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 14 entries, 0 to 13
Data columns (total 4 columns):
 #   Column      Non-Null Count  Dtype         
---  ------      --------------  -----         
 0   event_name  14 non-null     object        
 1   region      14 non-null     object        
 2   start_dt    14 non-null     datetime64[ns]
 3   finish_dt   14 non-null     datetime64[ns]
dtypes: datetime64[ns](2), object(2)
memory usage: 576.0+ bytes
In [8]:
df_viewing(df_user)
user_id first_date region device
0 D72A72121175D8BE 2020-12-07 EU PC
1 F1C668619DFE6E65 2020-12-07 N.America Android
2 2E1BF1D4C37EA01F 2020-12-07 EU PC
3 50734A22C0C63768 2020-12-07 EU iPhone
4 E1BDDCE0DAFA2679 2020-12-07 N.America iPhone
---------------------------------------------------------------------------------------------------------
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 61733 entries, 0 to 61732
Data columns (total 4 columns):
 #   Column      Non-Null Count  Dtype 
---  ------      --------------  ----- 
 0   user_id     61733 non-null  object
 1   first_date  61733 non-null  object
 2   region      61733 non-null  object
 3   device      61733 non-null  object
dtypes: object(4)
memory usage: 1.9+ MB
None
---------------------------------------------------------------------------------------------------------
                 user_id  first_date region   device
count              61733       61733  61733    61733
unique             61733          17      4        4
top     D72A72121175D8BE  2020-12-21     EU  Android
freq                   1        6290  46270    27520
---------------------------------------------------------------------------------------------------------
user_id       0
first_date    0
region        0
device        0
dtype: int64
---------------------------------------------------------------------------------------------------------
0

В df_user-все пользователи, зарегистрировавшиеся в интернет-магазине в период с 7 по 21 декабря 2020 года; 61713 , требуется преобразовать столбец со временем

In [9]:
df_user['first_date'] = pd.to_datetime(df_user['first_date'])
In [10]:
df_user.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 61733 entries, 0 to 61732
Data columns (total 4 columns):
 #   Column      Non-Null Count  Dtype         
---  ------      --------------  -----         
 0   user_id     61733 non-null  object        
 1   first_date  61733 non-null  datetime64[ns]
 2   region      61733 non-null  object        
 3   device      61733 non-null  object        
dtypes: datetime64[ns](1), object(3)
memory usage: 1.9+ MB
In [11]:
df_viewing(df_event)
user_id event_dt event_name details
0 E1BDDCE0DAFA2679 2020-12-07 20:22:03 purchase 99.99
1 7B6452F081F49504 2020-12-07 09:22:53 purchase 9.99
2 9CD9F34546DF254C 2020-12-07 12:59:29 purchase 4.99
3 96F27A054B191457 2020-12-07 04:02:40 purchase 4.99
4 1FD7660FDF94CA1F 2020-12-07 10:15:09 purchase 4.99
---------------------------------------------------------------------------------------------------------
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 440317 entries, 0 to 440316
Data columns (total 4 columns):
 #   Column      Non-Null Count   Dtype  
---  ------      --------------   -----  
 0   user_id     440317 non-null  object 
 1   event_dt    440317 non-null  object 
 2   event_name  440317 non-null  object 
 3   details     62740 non-null   float64
dtypes: float64(1), object(3)
memory usage: 13.4+ MB
None
---------------------------------------------------------------------------------------------------------
            details
count  62740.000000
mean      23.877631
std       72.180465
min        4.990000
25%        4.990000
50%        4.990000
75%        9.990000
max      499.990000
---------------------------------------------------------------------------------------------------------
user_id            0
event_dt           0
event_name         0
details       377577
dtype: int64
---------------------------------------------------------------------------------------------------------
0

В df_event-все события новых пользователей в период с 7 декабря 2020 по 4 января 2021 года; Меняю тип данных в столбце время и изучаю 70% пропусков в стобце details. Добавлю столбец с датой

In [12]:
df_event['event_dt'] = pd.to_datetime(df_event['event_dt'])
In [13]:
df_event['date'] = df_event['event_dt'].dt.date
In [14]:
df_event['date'] = pd.to_datetime(df_event['date'], format='%Y-%m-%d')
In [15]:
df_event.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 440317 entries, 0 to 440316
Data columns (total 5 columns):
 #   Column      Non-Null Count   Dtype         
---  ------      --------------   -----         
 0   user_id     440317 non-null  object        
 1   event_dt    440317 non-null  datetime64[ns]
 2   event_name  440317 non-null  object        
 3   details     62740 non-null   float64       
 4   date        440317 non-null  datetime64[ns]
dtypes: datetime64[ns](2), float64(1), object(2)
memory usage: 16.8+ MB
In [16]:
df_event['event_name'].unique()
Out[16]:
array(['purchase', 'product_cart', 'product_page', 'login'], dtype=object)
In [17]:
df_event.tail()
Out[17]:
user_id event_dt event_name details date
440312 245E85F65C358E08 2020-12-30 19:35:55 login NaN 2020-12-30
440313 9385A108F5A0A7A7 2020-12-30 10:54:15 login NaN 2020-12-30
440314 DB650B7559AC6EAC 2020-12-30 10:59:09 login NaN 2020-12-30
440315 F80C9BDDEA02E53C 2020-12-30 09:53:39 login NaN 2020-12-30
440316 7AEC61159B672CC5 2020-12-30 11:36:13 login NaN 2020-12-30
In [18]:
df_event.groupby('event_name')['details'].count()
Out[18]:
event_name
login               0
product_cart        0
product_page        0
purchase        62740
Name: details, dtype: int64

Пропуски во всех событиях кроме purchase, востановить не возможно

In [19]:
df_viewing(df_participant)
user_id group ab_test
0 D1ABA3E2887B6A73 A recommender_system_test
1 A7A3664BD6242119 A recommender_system_test
2 DABC14FDDFADD29E A recommender_system_test
3 04988C5DF189632E A recommender_system_test
4 482F14783456D21B B recommender_system_test
---------------------------------------------------------------------------------------------------------
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 18268 entries, 0 to 18267
Data columns (total 3 columns):
 #   Column   Non-Null Count  Dtype 
---  ------   --------------  ----- 
 0   user_id  18268 non-null  object
 1   group    18268 non-null  object
 2   ab_test  18268 non-null  object
dtypes: object(3)
memory usage: 428.3+ KB
None
---------------------------------------------------------------------------------------------------------
                 user_id  group            ab_test
count              18268  18268              18268
unique             16666      2                  2
top     0FDFDA0B2DEC2D91      A  interface_eu_test
freq                   2   9655              11567
---------------------------------------------------------------------------------------------------------
user_id    0
group      0
ab_test    0
dtype: int64
---------------------------------------------------------------------------------------------------------
0

В df_participant-таблица участников тестов. Пропусков, дубликатов нет. Уникальных пользователей меньше, чем строк в этом датасете, значит некоторые пользовател вошли в разные группы/тесты

In [20]:
df_participant['ab_test'].unique()
Out[20]:
array(['recommender_system_test', 'interface_eu_test'], dtype=object)
In [21]:
df_participant['group'].unique()
Out[21]:
array(['A', 'B'], dtype=object)
In [22]:
df_participant.groupby('group')['ab_test'].count()
Out[22]:
group
A    9655
B    8613
Name: ab_test, dtype: int64
In [23]:
t = df_participant.query('ab_test=="recommender_system_test"')
In [24]:
t.groupby('group')['ab_test'].count()
Out[24]:
group
A    3824
B    2877
Name: ab_test, dtype: int64
In [25]:
df_user_t = t['user_id'].nunique()
In [26]:
users_both_groups = df_participant.groupby('user_id').filter(lambda x: x['group'].nunique() == 2)
users_both_groups.head()
Out[26]:
user_id group ab_test
25 EAFB9027A27D510C B recommender_system_test
34 C758C53D624AD1AC B recommender_system_test
35 8C478EE1AE7C98A4 B recommender_system_test
44 FC3F3E4DA7C85F88 A recommender_system_test
49 FE2AF0E94DBD470E A recommender_system_test
In [27]:
users_both_count = users_both_groups['user_id'].count()

Итого 1552 пользователя входят одновременно и в группу А и В

In [28]:
df_participant.query('user_id=="EAFB9027A27D510C"')
Out[28]:
user_id group ab_test
25 EAFB9027A27D510C B recommender_system_test
11652 EAFB9027A27D510C A interface_eu_test
In [29]:
df_participant.query('user_id=="CE782A3646E8E5E1"')
Out[29]:
user_id group ab_test
1744 CE782A3646E8E5E1 B recommender_system_test
18258 CE782A3646E8E5E1 A interface_eu_test

Посмотрим для нашего теста

In [30]:
users_both_recommender = t.groupby('user_id').filter(lambda x: x['group'].nunique() == 2)
users_both_recommender
Out[30]:
user_id group ab_test

Проверим процент попавших и в наш тест и в соседний

In [31]:
procent_user = round(users_both_count/df_user_t*100,1)
In [32]:
print(f"Процент пользователей попавших в обе группы от общего количества: {procent_user}%")
Процент пользователей попавших в обе группы от общего количества: 23.2%

В нашем тесте(recommender_system_test) таких нет. То есть пользователь входит и в разные группы но и в разные тесты. И таких 23,2% от общего числа, участников теста. Удалить такое количество не получиться

Так как на контрольную группу А воздействия не оказывается, то пересечение не влияет на результат теста, проверим пересечение только в группе В

In [33]:
t_group_b = df_participant.query('group=="B"')
In [34]:
users_both_t_group_b = t_group_b.groupby('user_id').filter(lambda x: x['ab_test'].nunique() == 2)
users_both_t_group_b
Out[34]:
user_id group ab_test
29 5D5E6EE92AF6E9E0 B recommender_system_test
53 952D1EEBF552BC95 B recommender_system_test
79 B3C1FF8D21EAC16B B recommender_system_test
105 04BE4EFE4C457312 B recommender_system_test
124 A0794039D643F1B6 B recommender_system_test
... ... ... ...
18079 8FF8F87305BB9A7D B interface_eu_test
18094 C2025648F77BD80B B interface_eu_test
18170 7DF21AEB1AA231F9 B interface_eu_test
18183 EA6EA431FF84563B B interface_eu_test
18245 2B0CD24EE4291CA0 B interface_eu_test

688 rows × 3 columns

In [35]:
t_group_b.query('user_id=="5D5E6EE92AF6E9E0"')
Out[35]:
user_id group ab_test
29 5D5E6EE92AF6E9E0 B recommender_system_test
14113 5D5E6EE92AF6E9E0 B interface_eu_test
In [36]:
t_group_b.query('user_id=="7DF21AEB1AA231F9"')
Out[36]:
user_id group ab_test
3868 7DF21AEB1AA231F9 B recommender_system_test
18170 7DF21AEB1AA231F9 B interface_eu_test

Пересечение пользователей по тестовой группе 688 пользователей.

In [37]:
user_t_b = users_both_t_group_b['user_id'].nunique()
In [38]:
df_partition_b = df_participant.query('group=="B"')
df_user_b = df_partition_b['user_id'].nunique()
In [39]:
proc_user_b = round(user_t_b/df_user_b*100,2)
print(f"Процент пользователей группы В от общего числа пользователей группы В обоих тестов: {proc_user_b}%")
Процент пользователей группы В от общего числа пользователей группы В обоих тестов: 4.16%
In [40]:
t_group_a = df_participant.query('group=="A"')
users_both_t_group_a = t_group_a.groupby('user_id').filter(lambda x: x['ab_test'].nunique() == 2)
user_t_a = users_both_t_group_a['user_id'].nunique()
df_partition_a = df_participant.query('group=="A"')
df_user_a = df_partition_a['user_id'].nunique()
proc_user_a = round(user_t_a/df_user_a*100,2)
print(f"Процент пользователей группы A от общего числа пользователей группы В обоих тестов: {proc_user_a}%")
Процент пользователей группы A от общего числа пользователей группы В обоих тестов: 5.25%

Процент попавших в обе группы достаточно равномерный, поэтому и воздействие на группы оказывает равномерное

Пересечение с конкурирующим тестом, без разбивки на группы

In [41]:
users_both_test = df_participant.groupby('user_id').filter(lambda x: x['ab_test'].nunique() == 2)
len(users_both_test)
Out[41]:
3204
In [42]:
arew_user = df_participant.query('ab_test == "recommender_system_test"')['user_id'].unique()
unis_user = df_participant.query('ab_test == "interface_eu_test"')['user_id'].unique()

common_users = np.intersect1d(arew_user, unis_user)
len(common_users)
Out[42]:
1602

1602 Пользователя одновременно входят и в наш тест и в конкурирующий

внутри нашего теста распределение пользователей из второй тестовой группы между группами нашего теста

In [43]:
arew_common = df_participant[(df_participant['user_id'].isin(common_users)) & (df_participant['ab_test'] == 'recommender_system_test')]
group_counts = arew_common.groupby('group')['user_id'].nunique()
group_counts
Out[43]:
group
A    921
B    681
Name: user_id, dtype: int64
In [44]:
arew_common_a = arew_common.query('group=="A"')['user_id'].nunique()
arew_common_b = arew_common.query('group=="B"')['user_id'].nunique()
In [45]:
df_rec_test_aa = df_participant.query('ab_test=="recommender_system_test" & group=="A"')['user_id'].nunique()
df_rec_test_bb = df_participant.query('ab_test=="recommender_system_test" & group=="B"')['user_id'].nunique()
In [46]:
proc_a_rec = round(arew_common_a/df_rec_test_aa*100,2)
print(f"Процент пользователей группы А от общего числа пользователей группы А  нашего теста: {proc_a_rec}%")
Процент пользователей группы А от общего числа пользователей группы А  нашего теста: 24.08%
In [47]:
proc_user_b_rec = round(arew_common_b/df_rec_test_bb*100,2)
print(f"Процент пользователей группы В от общего числа пользователей группы В нашего теста: {proc_user_b_rec}%")
Процент пользователей группы В от общего числа пользователей группы В нашего теста: 23.67%

Около 24% пользователей из группы А конкурирующего теста входят в группу А нашего теста, и 23.5% по группе В

Проверка соответствию данных ТЗ¶

In [48]:
df_ab_filt = df_ab[df_ab['region'].str.contains('EU')]
df_ab_filt = df_ab_filt.query('start_dt>="2020-12-07" and finish_dt<="2021-01-04"')
df_ab_filt
Out[48]:
event_name region start_dt finish_dt
0 Christmas&New Year Promo EU, N.America 2020-12-25 2021-01-03

Только одно мероприятие проходило в период и регионе проведения нашего теста. С 25/12 по 03/01

In [49]:
print(df_user['first_date'].min())
print(df_user['first_date'].max())
2020-12-07 00:00:00
2020-12-23 00:00:00

Фильтрую новых пользователей про региону и дате

In [50]:
df_user['region'].unique()
Out[50]:
array(['EU', 'N.America', 'APAC', 'CIS'], dtype=object)
In [51]:
df_user_f = df_user.query('region=="EU" and "2020-12-07"<=first_date<="2020-12-21"').reset_index()
In [52]:
df_user_f.head()
Out[52]:
index user_id first_date region device
0 0 D72A72121175D8BE 2020-12-07 EU PC
1 2 2E1BF1D4C37EA01F 2020-12-07 EU PC
2 3 50734A22C0C63768 2020-12-07 EU iPhone
3 7 8942E64218C9A1ED 2020-12-07 EU PC
4 9 FFCEA1179C253104 2020-12-07 EU Android
In [53]:
print(df_user_f['first_date'].min())
print(df_user_f['first_date'].max())
print(df_user_f['user_id'].nunique())
2020-12-07 00:00:00
2020-12-21 00:00:00
42340

Отфильтровал пользователей по дате и региону, осталось 42340 новых пользователей. Данные по пользователям соответствую нашему ТЗ набирались с 07-12 по 21-12

Фильтрую события

In [54]:
df_event.head()
Out[54]:
user_id event_dt event_name details date
0 E1BDDCE0DAFA2679 2020-12-07 20:22:03 purchase 99.99 2020-12-07
1 7B6452F081F49504 2020-12-07 09:22:53 purchase 9.99 2020-12-07
2 9CD9F34546DF254C 2020-12-07 12:59:29 purchase 4.99 2020-12-07
3 96F27A054B191457 2020-12-07 04:02:40 purchase 4.99 2020-12-07
4 1FD7660FDF94CA1F 2020-12-07 10:15:09 purchase 4.99 2020-12-07
In [55]:
print(df_event['date'].min())
print(df_event['date'].max())
print(df_event['user_id'].nunique())
2020-12-07 00:00:00
2020-12-30 00:00:00
58703

Данные в тесте только с 07-12 по 30-12, учитывая что наш тест продолжался до 04-01 то данных за 5 дней не достает. Востановить данные не возможно. Пользователей 58703. Обьединяю таблицы с событиями и новыми пользователями, тогда отсечем всех старых кто есть в датасете события и из датасета новые юзеры всех кто не совершал ни одного события

In [56]:
df_user_event = df_user_f.merge(df_event, on='user_id', how='left').drop('index', axis=1)
In [57]:
df_user_event.head()
Out[57]:
user_id first_date region device event_dt event_name details date
0 D72A72121175D8BE 2020-12-07 EU PC 2020-12-07 21:52:10 product_page NaN 2020-12-07
1 D72A72121175D8BE 2020-12-07 EU PC 2020-12-07 21:52:07 login NaN 2020-12-07
2 2E1BF1D4C37EA01F 2020-12-07 EU PC 2020-12-07 09:05:47 product_cart NaN 2020-12-07
3 2E1BF1D4C37EA01F 2020-12-07 EU PC 2020-12-10 04:13:53 product_cart NaN 2020-12-10
4 2E1BF1D4C37EA01F 2020-12-07 EU PC 2020-12-12 17:54:57 product_cart NaN 2020-12-12
In [58]:
df_user_event['user_id'].nunique()
Out[58]:
42340

Создали таблицу с новыми пользователями и событиями которые он совершили, исходя из тех. задания. Таблицу с участниками нашего теста мы проверяли ранее. Наш тест recommender_system_test есть в этой таблице и также есть наши группы А и В

In [59]:
df_user_event_t = df_user_event.merge(t, on='user_id', how='left')
In [60]:
df_user_event_t.head()
Out[60]:
user_id first_date region device event_dt event_name details date group ab_test
0 D72A72121175D8BE 2020-12-07 EU PC 2020-12-07 21:52:10 product_page NaN 2020-12-07 A recommender_system_test
1 D72A72121175D8BE 2020-12-07 EU PC 2020-12-07 21:52:07 login NaN 2020-12-07 A recommender_system_test
2 2E1BF1D4C37EA01F 2020-12-07 EU PC 2020-12-07 09:05:47 product_cart NaN 2020-12-07 NaN NaN
3 2E1BF1D4C37EA01F 2020-12-07 EU PC 2020-12-10 04:13:53 product_cart NaN 2020-12-10 NaN NaN
4 2E1BF1D4C37EA01F 2020-12-07 EU PC 2020-12-12 17:54:57 product_cart NaN 2020-12-12 NaN NaN
In [61]:
df = df_user_event.merge(t, on='user_id', how='left')
In [62]:
df_new = t.merge(df_user_event, on='user_id', how='inner')
In [63]:
df_new.head()
Out[63]:
user_id group ab_test first_date region device event_dt event_name details date
0 D1ABA3E2887B6A73 A recommender_system_test 2020-12-07 EU PC 2020-12-07 14:43:27 purchase 99.99 2020-12-07
1 D1ABA3E2887B6A73 A recommender_system_test 2020-12-07 EU PC 2020-12-25 00:04:56 purchase 4.99 2020-12-25
2 D1ABA3E2887B6A73 A recommender_system_test 2020-12-07 EU PC 2020-12-07 14:43:29 product_cart NaN 2020-12-07
3 D1ABA3E2887B6A73 A recommender_system_test 2020-12-07 EU PC 2020-12-25 00:04:57 product_cart NaN 2020-12-25
4 D1ABA3E2887B6A73 A recommender_system_test 2020-12-07 EU PC 2020-12-07 14:43:27 product_page NaN 2020-12-07
In [64]:
df_user_unique_t = df_user_event_t['user_id'].nunique()
In [65]:
df_user_event_t['ab_test'].unique()
Out[65]:
array(['recommender_system_test', nan], dtype=object)
In [66]:
df_user_event_t.isna().sum()
Out[66]:
user_id            0
first_date         0
region             0
device             0
event_dt        2874
event_name      2874
details       260609
date            2874
group         279237
ab_test       279237
dtype: int64

Ожидаемое количество участникв теста

In [67]:
filtered_df_t = df_new['user_id'].nunique()
In [68]:
nan_percent = round(filtered_df_t/df_user_unique_t*100,1)

print(f"Уникальных пользователей: {filtered_df_t}")
print(f"Процент пользователей участвовавших в нашем эксперименте 'test': {nan_percent}%")
Уникальных пользователей: 6351
Процент пользователей участвовавших в нашем эксперименте 'test': 15.0%

Только около 15.0 % новых пользователей участвовало в нашем тесте, от общего числа зарегестрировынных в период проведения теста и регион EU

Ожидаемый эффект: за 14 дней с момента регистрации в системе пользователи покажут улучшение каждой метрики не менее, чем на 5 процентных пунктов:

  • конверсии в просмотр карточек товаров — событие product_page
  • просмотры корзины — product_cart
  • покупки — purchase

(Так как данные по 30-12 только, то мы ищем возраст события, из обьединеной таблицы с участника тестов из Европы и мы можем для каждого события посчитать возраст, есть регистрации пользователей и день совершения события, из дня совершения события вычитаем день регистрации и получаем тот день на которое было совершено событие, те 0 значит событие = регистрации. Момент, не все пользоватли совершали события, нужно проанализировать сколько их из группы А и В. После этого фильтруем наш ДС с возрастом <=13 дней. Учесть NaN, и получить отфильтроынный датасет. И проверить нужныли были 14 дней, строим гистограмма х возраст и у количество событий двойная для обоих групп.)

In [69]:
df_ab_group = df_user_event_t.query('ab_test=="recommender_system_test"')
df_ab_group.groupby('group')['user_id'].count()
Out[69]:
group
A    19339
B     6951
Name: user_id, dtype: int64
In [70]:
df_ab_group = df_ab_group.reset_index(drop=True)
In [71]:
df_ab_group['day_count'] = df_ab_group['date'] - df_ab_group['first_date']
df_ab_group['day_count'] = df_ab_group['day_count'].apply(lambda x: x.days)
In [72]:
df_ab_group['day_count'].value_counts()
Out[72]:
0.0     7719
1.0     3559
2.0     2451
3.0     1712
4.0     1417
5.0     1149
6.0      999
7.0      924
8.0      725
9.0      578
10.0     481
12.0     341
11.0     322
13.0     243
14.0     208
15.0     163
16.0      94
17.0      78
18.0      77
20.0      62
19.0      54
21.0      31
22.0      29
23.0       4
Name: day_count, dtype: int64
In [73]:
df_ab_group.head()
Out[73]:
user_id first_date region device event_dt event_name details date group ab_test day_count
0 D72A72121175D8BE 2020-12-07 EU PC 2020-12-07 21:52:10 product_page NaN 2020-12-07 A recommender_system_test 0.0
1 D72A72121175D8BE 2020-12-07 EU PC 2020-12-07 21:52:07 login NaN 2020-12-07 A recommender_system_test 0.0
2 E6DE857AFBDC6102 2020-12-07 EU PC NaT NaN NaN NaT B recommender_system_test NaN
3 DD4352CDCF8C3D57 2020-12-07 EU Android 2020-12-07 15:32:54 product_page NaN 2020-12-07 B recommender_system_test 0.0
4 DD4352CDCF8C3D57 2020-12-07 EU Android 2020-12-08 08:29:31 product_page NaN 2020-12-08 B recommender_system_test 1.0
In [74]:
df_ab_group.groupby(['user_id','event_name'])['day_count'].count()
Out[74]:
user_id           event_name  
001064FEAAB631A1  login           3
                  product_page    3
0010A1C096941592  login           4
                  product_page    4
                  purchase        4
                                 ..
FFAE9489C76F352B  login           3
                  product_page    3
FFF28D02B1EACBE1  login           3
                  product_cart    3
                  product_page    3
Name: day_count, Length: 7767, dtype: int64
In [75]:
df_ab_group.groupby('user_id')['event_name'].count()
Out[75]:
user_id
000ABE35EE11412F     0
001064FEAAB631A1     6
0010A1C096941592    12
001C05E87D336C59     0
00341D8401F0F665     2
                    ..
FFC2C5F898D1245B     0
FFC53FD45DDA5EE8     0
FFE858A7845F005E     0
FFED90241D04503F     0
FFF28D02B1EACBE1     9
Name: event_name, Length: 6351, dtype: int64
In [76]:
def group_and_ungroup(x):
    return x.day_count.count(), np.sum(x.day_count)

grouped = df_ab_group.groupby(['user_id', 'event_name']).apply(group_and_ungroup)
In [77]:
grouped
Out[77]:
user_id           event_name  
001064FEAAB631A1  login            (3, 7.0)
                  product_page     (3, 7.0)
0010A1C096941592  login           (4, 12.0)
                  product_page    (4, 12.0)
                  purchase        (4, 12.0)
                                    ...    
FFAE9489C76F352B  login            (3, 8.0)
                  product_page     (3, 8.0)
FFF28D02B1EACBE1  login           (3, 13.0)
                  product_cart    (3, 13.0)
                  product_page    (3, 13.0)
Length: 7767, dtype: object
In [78]:
user_counts = df_ab_group.groupby('user_id')['event_name'].nunique()
single_event_users = user_counts[user_counts == 1].index.tolist()
print(single_event_users)
['00341D8401F0F665', '00505E15A9D81546', '00A52DCF85F1BE03', '016F758EB5C5A5DA', '01BD731FB5E57AE6', '0205944A5D3DC004', '0225F4E8E0D98BA4', '0240DB73D9B6F136', '0386BA6137787A24', '040DB681B8CFAACA', '04BE4EFE4C457312', '04F2CF340B4F3822', '051D59BC38C3B3AA', '05CB67D01740EF6E', '0657ECDBA63E5FD6', '06C9AD50396A67F2', '08DF1B2539351AAB', '09BD5F7225CD0F7E', '0A5E3BE3C51CB47A', '0B08BA67D85527C5', '0C2FF828F063B7AD', '0CA61E320CBC8D4F', '0CA93D3A4A1D8389', '0E77217AC0A85CB2', '0EBE4BDE16D19DC0', '0F1349E8B6D23E43', '0F6573B9431935D2', '0F7D49FC184EDCDE', '0FBF679EA1340786', '0FC3C8BC1348A405', '0FF10274AC68852A', '10018FAD7C1BEB3E', '102613D89170C020', '10387B8D694D539C', '1042C7577F706938', '104E2E7457341647', '10C645F9BDAD5E5B', '10EA2E44272E32A8', '111E0B8A76E90532', '11970FE3A608F9EF', '122C0E3A8F27AC93', '127DA1103176E88E', '12C0F58AEECAEA2F', '12C4599F88ABEC86', '12FCEFC7D1907D47', '135F6688A1DC637B', '1381632DA8AD52C0', '139519F1ADB40E0D', '13E1E6999FEA8C82', '14274CC0FEE00801', '1484BBF124DB1B18', '14AEC962812EB706', '154228E9B2DA7F16', '1544489D5476AD59', '15F57595AB51A15C', '1671B60B525AD59C', '16B827FC823C52A8', '16E9BCCA73DF4AF5', '16F2DB656CC2F952', '172F0C1F993BE914', '173B960BB44D4E22', '181181D6E7BA82B4', '181DA774FA96EDB6', '1839CDFE4E9E10B3', '18D0F694EA816E7D', '196E3C3541EB8BE1', '1AA90C1AD5727610', '1BC8D784255D5E67', '1C5874DE0D952A0D', '1C711C65356BCFBF', '1D7745C3BC072443', '1D95C80183156D87', '1E7B931452B2F965', '1E7CED0F8870180C', '1E8329A6915FB27E', '1EB1C8E41C1DD5EE', '1F2C38ADEF57670B', '1F8FAE6372EB9C6C', '20155135A98368DA', '21C2D41E1FDF1E70', '21E6AD81A122B126', '22092C653019E91C', '239838E03D995168', '23DDD27AC3FEFA63', '23F551153080EBAB', '249230DC3628A7AE', '24D35A2E59D68D66', '24D3AAA91EFD597F', '2599F6408D61FAD1', '25B6EE0F91D32DB9', '2626A4773033458D', '2686619F5A2AE06C', '2694629861CC9154', '26AB15B163D012E4', '26B0521FC2BAB2D1', '27796B536E6C0F6D', '2784913F3447A9EE', '2853F21A0E52F7AA', '28D889F742D7CB1F', '28F165B39D160BC5', '2901C103BDFD8B58', '2905EDBC782A9E69', '2A114ED4FE2F5FED', '2A12393A2BBFF701', '2A7220F590E2EB8E', '2A7E32FA8EF4DBA1', '2B68D50DAADB1DC4', '2C5E1D23A27078A0', '2C8D5D38742CD38D', '2CBFC548B33C0259', '2CC15FB4F021D006', '2CDFC246EAEEAB13', '2CEC84A86958E639', '2D53E02A0E04DC84', '2E53814463A4BE74', '2EBA55084462645A', '2ED365A23D725DB0', '2F9EC4B16D68E2F1', '30D6A716BEDA75C8', '312C37647F3D5433', '315CB3A5EA257B8E', '31D4A79415209872', '32244E379E754915', '3243339CAFFA806B', '327EB6851FEAD2E9', '33CEAE52626CA99E', '340D9C518D3CBB8D', '34BBBAC1CCC0F4F9', '35795D0E78C18AE4', '35EF0E3272713CFC', '3656B8F3DEF02DA0', '375CA26C16C90078', '38B78992508A9283', '38BCEA4860D38AA9', '38DA0BA08A977520', '39E330A497886B80', '3A7987AFAA3A5F47', '3AE5E3EE2C8A8727', '3B2470E45416C59A', '3B3AE26404B64EF2', '3B3F68DC5779E517', '3B4FF434FA8193B6', '3C02D15BA7E8CD69', '3C7868D5FEDB39F7', '3D264F3D953DBA67', '3D8509A61CEED2B2', '3D960D370AF76942', '3E2016ED6EDB5841', '3ED90BE0DC2A3FD3', '3F52D53AB33C9FD6', '3FAA6AD64C3B47E8', '4089CD204802F5F8', '40C11E04E846C139', '4122B8F30EB2271C', '41720B0DC37FACD5', '4266741E592070B6', '427C5593BE58CA0A', '42ECCBA921AFE086', '42FC1295136570BA', '436D6F820AE149EA', '43D07273336C97BC', '4400B2091A6E2CCD', '44037F187E376CD3', '449330634C4DD2EA', '44BD1495B3FB208B', '4566C623449592A7', '45D971502E89E65E', '463D4D4948E0D4A4', '465BEC2A7190853B', '46BDEF99D733A7B7', '46C03A54CD7F3F6E', '479E6D6A1693C32E', '47A0EAE52F635E59', '47A4DAD2DBC2DDA0', '47ADBF580CF107E1', '47FD6B1D75C29C53', '48526B8C7F8459FD', '489B8E34D1E65CCA', '48FEAA2BDBC721D4', '4949578BF1954CF5', '49C52F2638D25D2F', '4A53CB855B71A848', '4A61A28F35795FD1', '4A9D5A8F91CA5C62', '4B479E626904CF59', '4B60670F850D5024', '4B6BA21C73F66940', '4C30B83191F4E868', '4CAED71575E5EA2F', '4CCFDC7BE9B6FE73', '4CF8E8855FB1A493', '4CFBECEF398176DA', '4D921165CD2825D1', '4DE200C2E6892148', '4E43940B0CF8D947', '4E5C9F61B66394F7', '4EB733A6B2C93533', '4ECCD93E1F247611', '4F556AAB06E086E5', '4FFEF3C8471FF8EA', '505D67B4B604CF79', '50CF32FEE0C61DA9', '51560FF11CCA1F74', '51CFE2034A743204', '51DA2C882D0D7508', '5207E56E697027E9', '52828376E649CA27', '52A0547B1B31907F', '52E7B7DC39F52987', '535DC74493925CF8', '53638472C31218BA', '53732758D90FB1E4', '5426EFD4BBAEA349', '542DD6DB81108686', '54E564A7253C7084', '552AF033F097DE56', '55978D947D078E6A', '55F1DDF55DD42893', '564975AFF8E15C0C', '565EF59DDD36B95B', '57150D3838DE301A', '5732FDB1C948FE26', '57442DD4178AFE27', '576F0756184C8DB1', '583CC6A8A1A8F809', '58D06B81851522E6', '58F29FD5F5BEEF1E', '59322F3BAE5D0668', '597F7FD5AA3524D3', '59FF17724B8BCE48', '5A3B4228AD33781F', '5A3CFCB7782BB05F', '5AD2A85FFA7D7EBE', '5B05449E69A2AD9E', '5B0EB7BAB456C0FE', '5B4EFE916AD19741', '5B5F8713DF915802', '5B671D912F9D48BE', '5BDF2A772826F42C', '5C909C0F25ACD34E', '5C9E46262BBA9A24', '5D93D429975AAA1D', '5E4F77BDD1723F58', '5E6B0B086089C4CF', '5E9E80BADEA372AB', '5F09B85B875AA41D', '5F89649C6337CE7D', '5FE8A7964BFE9201', '5FED9C8EB157278D', '6070727198404A40', '60A008C4D5A3B8F2', '6184849918C8A94A', '619D3C459732BFDC', '633CB6082D5FF13A', '6363273F6349DBE6', '64597B30278462F5', '64E669C58B152047', '66132FEF55AE5854', '669F197DC7EB4FD9', '66BBDB594A250B3B', '66C2398A8C8A8335', '66FC298441D50783', '67498C0E4D4F1697', '6749FD012B7C93FD', '681FEAB4C02403C9', '68775744A4EB8649', '692705CAB94C3834', '693A3CB45929D1B2', '69924C768EE3374C', '69E475C5708ABE56', '6A01DD5AF15B5187', '6A07E9483E1D2638', '6A581C74EF4D5F44', '6A698019769B0F16', '6A7BCBD9BB3A3097', '6A7F8D10E390B4F5', '6ADFC435DBE4D6EF', '6B03981851818A0F', '6B4FA9A39FC379E8', '6CEE9CA228CCBA3C', '6D88BE6410DBB984', '6D98E5AEC0DCFF0B', '6DB2AB2869590A63', '6DECB18EA848DDDA', '6E5952194C02A488', '6E75BB608BDBFD49', '6EA1A93DD87FC2B7', '6F5F0C6161DCC360', '6FC1860C26779058', '701B5B6292603C20', '702566DE3898CB6C', '70BF82527E6ED9C3', '71DC282C52CFC244', '72742C5F312A1FEC', '729F6099B1B31935', '7342090A9E50B657', '74285DE881A72F4C', '743EFA16F928E18B', '747A57ED229B9235', '74DA40C35F685C4E', '74EEC94A70685C1E', '75AC3DC6E14554D0', '75C1434A74B03290', '75CBBBB56CFC020E', '76174428B80A08A0', '762F7B71CE6B0BF0', '768C51C0B882AB6D', '770B5C9E0E546E44', '77E438939CEADFAB', '782A69A1E16ACB23', '788724F2A10C59FE', '78CE39387962B153', '7963560AC5D4166E', '79656F14758D76BA', '798E2463C84ECAD7', '7A504D510F9703FB', '7A6E345A0058C71B', '7B0A1B548046E35B', '7B8F59B55EB43A91', '7BC59B90C9447C42', '7C67ABA421AF74D5', '7C9CA3F88B8755F1', '7CAC1DCC440BB214', '7CBE6C0B1530D0C8', '7CEFBD3C6ABEF91A', '7D346928024E5D79', '7D4C6A8C20B204F1', '7DD97D3E8E2C5E85', '7E26530508267F69', '7F2C298110AA73B4', '7FF809153882541B', '80055F949A01DDAD', '8013F2257A25F9BD', '80256304BFCD58C8', '802AE3764E188FC6', '8039D09D1E1B382C', '80D722A2C625E8B9', '814DF3DA9DD71B8A', '81548C930A397EBC', '818AEF8DE8B21EA7', '819449F677945F98', '82087A12CCB2F2AD', '82240827D9FAFDBC', '823BE193CDC32ADF', '82952C2B5CFAD42B', '82AE150FE103247A', '82E92E637C47885A', '83326C8A4BDB6BE1', '83E9CFC64D61108E', '846ABAF7694AA1A3', '84EB34779A251529', '851F6015525A2374', '856CDD7A9ECC45B2', '85D9DB7B0BC24D2A', '8683850A677DCC7A', '87B112DF92E5A3BA', '87FFD32D80DA1933', '894400D17C4E34C2', '8A2FACABB19EDC70', '8A401E7C2E0DAE58', '8A494F7F70AA2A2A', '8AA7482655CA47FC', '8AAB656D95891646', '8AE7EBC9CA45EEB7', '8B2134D31A514E4E', '8B44602F93F6CFB7', '8BF5CA0B2A04EB04', '8C0C9BBA51E96C48', '8C5E672BF19D5DEE', '8C66932374EF8009', '8C711C385D747C84', '8C88FA30D91583F7', '8CBBDC8958E20A2F', '8CE0BDF8F59A752E', '8D46D7EB5112DC9D', '8D4D2425F80F6A74', '8DC451FDE0E14B8C', '8DD7CC271D8CD039', '8DE1FF5E69F5124B', '8E0712757CE41DE4', '8E52B17EF9DCA68B', '8EE3018FC4D96F3F', '8F4577B4DEEAD5E1', '8F5B3D96E7D43A02', '8F98834C531143DA', '8FB9E95B030D25C5', '8FBAEB717ABA3930', '8FFA1D6F89AAEA40', '901C0498FF3E6796', '90BD4071AB4D6A78', '910E839ABF698914', '921FFC6F0F506A82', '92701F9DF59DC422', '928364C4C9F13FA8', '92CFDFB0A842D200', '93433E129ACF7E2D', '934A57222FCEEB83', '953EF42BFB998A53', '95401934D6D6D4FC', '954C980B4A9559FF', '95665DC1584AD532', '958426148AFA05C5', '95A9423B37553B4B', '966E398BAC214D29', '96749D41A2185B46', '9696D5EA6E1335C8', '9705F9A636A8C2C9', '971CD60FDB1BF254', '972CBA9F98130D80', '9745CE1D5B111CB1', '983051C6A972439D', '983D91483B077D54', '98949A08011E22F6', '992B70348B5E398C', '99A730464C4BB13C', '9A69D975A4ABE47C', '9A6CE800C60794BD', '9B234D350E686A67', '9B62277AA9225059', '9B73C945ABD15531', '9B8BFBACE614F651', '9BD9E79856D54059', '9C99E4676D6A8B44', '9D1710D562537AA8', '9D5D1CAC16E9262D', '9E537FA089BAECFE', '9EBDA30C87A7B9B3', '9F15AB8A7F968248', '9F17DAA9F8821330', '9F27772DC3EACA65', '9F4F58990EAB3DFE', '9FBB46EF36C35FD3', 'A070519278350129', 'A0ACD5C989AFEF92', 'A0C509818D9A54CA', 'A0ECB4825C9B8289', 'A0FCDFB113ABF06F', 'A121E8F760F7D482', 'A1BCBE234E188E49', 'A2505D1062E77B85', 'A2614E5A9AC15FC8', 'A27693967E58ADD5', 'A372F552AC2066B7', 'A3EDC1FFCB20AEAE', 'A3FE6D821476E810', 'A4E5755E0F7B094D', 'A517E4DD3ECFB0D9', 'A5ACC90F4E6C9D47', 'A62BEB5AF596DB62', 'A71444B5BEB5CCC4', 'A720D7FFE2FC85BC', 'A7957CDA89BDE94F', 'A7E7871D2D2637C4', 'A81BA2A7B5FD1862', 'A8533224E9384B16', 'A8738C98EA4BB1A7', 'A8C25A605F613665', 'A8C5F4D253DCEF2F', 'A907C114B5846EF0', 'A926366D229CB25C', 'A944D2BCDFD50619', 'AA5A1803D3FA76B4', 'AB0D9A9D709F476A', 'AB30C03FCEC72466', 'AB5EF9587051A99D', 'ABB61823786179CC', 'ABC0D4B6F3DE650C', 'ABDE941B896D8119', 'ACDCFF5D6EEDBEAC', 'ACDFC703CACAC820', 'AEB9B39AE54D3598', 'AF987F436A7EBD21', 'AF9FD44E4FBE7A44', 'AFF3D30CC385F0E9', 'AFFC4117AC279D49', 'B02C18F3C75E4F37', 'B05232A00CE7DA9B', 'B15D97BB5A951978', 'B1BEB31C7C6F966B', 'B2795EB0A968DFB5', 'B28C8D3E19E8A789', 'B2BB6F16652B21BF', 'B2D168BAC367B509', 'B3BD1DE2ACEEBC87', 'B4BA80B4BD6D550C', 'B4BAC8172F358933', 'B5006488F35ACDDB', 'B50ECB33FCECA482', 'B51F94359F04F11C', 'B54F288E9548E94E', 'B593E1E1E045C398', 'B5DC591BA6B42056', 'B5E7E222F1A5D7A6', 'B619C432EDEC453F', 'B6A3FD2473DDF30F', 'B6CFC956973D56CD', 'B7DBE079E5AB148E', 'B84DFF47E82A9FDF', 'B88CD7F1ECE6FAB6', 'B8F008F4479822AC', 'B956889B759F3E32', 'BA98FD8F84C838C7', 'BABB70C636BA523A', 'BAEFF105F449EDED', 'BAF6250F863EFA7E', 'BB7E54F566F3591B', 'BCF874A80C49209D', 'BD5FC2C0F26FC36D', 'BD7CF9238F6A1A07', 'BE161380813AA0F3', 'BE3C02D74A664180', 'BE8EF48E1C8A03BB', 'BEF16764A13AEC34', 'BF1EB8E6FA3F19B8', 'C04E36E535A6F740', 'C1EF154BABDC9A09', 'C21237DC47AE00C5', 'C2398EA0078EA63A', 'C2A2B2ADB29B8D74', 'C2F23593792D11D1', 'C3239111D782A4D5', 'C36784B608C9A693', 'C3C88EC13873148A', 'C472EA28E25BE6D7', 'C51F70F9545C0D4F', 'C55976AC94BFB22C', 'C5B80CE5D26813CF', 'C5C355D72A66F9FB', 'C632F81395C23676', 'C6647391F831C413', 'C68A124C91E59F64', 'C6FF68D218AAC396', 'C70D7F9553FF0C48', 'C72B62A241061928', 'C7750723906C925A', 'C77D7125A7F73F33', 'C799CEF5E1C8D488', 'C845E42170625895', 'C865891D80127AB8', 'C89E24F4C723CEF4', 'C8BDDF5639850737', 'C91282DA643F3ECA', 'C96DBA8C2C03EBC1', 'C99F4A393066D056', 'C9F4E51F24E6BCE1', 'CA05B60C5BF619D0', 'CA9D3DEA00836355', 'CB0255E90DB0FED4', 'CB5F0081A83B7397', 'CBC034EE5345D8BD', 'CC6C607F45AF7226', 'CC9D5FE5F9DC8D01', 'CDBDCFC2BE3CF712', 'CE3108804954FEC2', 'CE48F47093B03425', 'D0533ECF2A67B35C', 'D070FD0BFE78DFA4', 'D0D1426BAFDD1FE5', 'D0D8873714077818', 'D0ECAB9DA4FE2E00', 'D12A5C7D0C2AFCE6', 'D150AD7D66BFE61A', 'D1BB2F57BCD02BAE', 'D1D7C0CCE823D871', 'D2A9C2B69B9F4574', 'D2D8E67B7D0DE2D2', 'D2E647207224B3CB', 'D2F6FB926A64D196', 'D39EEEC785C51298', 'D40A89E14F1C10BD', 'D493E891D8F64BBC', 'D50BB2F89A4A8A71', 'D5DD44281759F7C8', 'D5DFEBAC8C8543A7', 'D63375DEBF111D81', 'D70EF43499FEBA88', 'D7663B1CF2680ECB', 'D78FF240E6D237B6', 'D7933558E33BF7D5', 'D857FFFE7A6AC9A4', 'D902807C00F2AF2D', 'DABC14FDDFADD29E', 'DAEE6D470B64E11B', 'DBCA5313FEE8A3B9', 'DBF9BDC859BC4459', 'DC24142B23D9FEDD', 'DC5EBC31C22D87B8', 'DD155919BA4AC26E', 'DD9369338455727C', 'DDF23E1CEC60F6C5', 'DE36357248F07700', 'DE7E58B105D38F62', 'DE98BA6D3814A6D4', 'DF9C2411ADA62C66', 'E09EE3450F1FDDEB', 'E12E58566E7B52DC', 'E1489CCA162BC2E6', 'E26CE7138789A7AF', 'E26F13A65CEAC6EA', 'E2E336528AB6202A', 'E2F981AE3D6A2CE8', 'E32D4C0315D6BFBE', 'E34104028365019C', 'E341ABC3EDB0AECD', 'E3481D64A20DA394', 'E3780F6861A69868', 'E3AF95C5CDBA8593', 'E3FE22D7C70B05E6', 'E40FA02A8A53F693', 'E4129EBDCA809BE8', 'E48564406C361C95', 'E53ECFC837EB8022', 'E594B711ADD81DD8', 'E5C1C86BCE045D73', 'E62E2664B4743026', 'E6911DABCAC3EFF2', 'E6AAFF21D051CC65', 'E6FE73B8B903B31B', 'E7692088C79C239E', 'E785E40D0C268712', 'E7F87F9FBA3269B1', 'E85E1F634E368569', 'E88883CDEF8783D6', 'E8D19649BE5927B3', 'E8D3E6385C8B875A', 'E93B6FE3C56ABA69', 'E9953B60E82F35E8', 'EA59CBC69BD2351F', 'EA62EC33715AE967', 'EA6EA431FF84563B', 'EAE7A5BE3C3D93B8', 'EBBD0BC203F21830', 'EBE2130151519EF6', 'EC9E9E250559033B', 'ECAE04CCCF52B16D', 'ECB7F5A1A2E1D00F', 'ED0814ECF62C56CA', 'ED11170D5BB9D744', 'ED47C7C2CD16539E', 'ED7297A4D3BFA953', 'ED9745F4A0B682B8', 'EDC7E08BC7DA267D', 'EDDE06B4239A55EE', 'EE9E527A761299B2', 'EF3118771FB74F92', 'EF449601C94D2496', 'F03F667009F16326', 'F04D6A0624CD2D1E', 'F0B92123E5142CBB', 'F0BD18FF153ED188', 'F10551276E3FDD53', 'F1B93BAE75EE0FB2', 'F24DA4C9406263D2', 'F26444BA1B35C213', 'F2CFD8007F2530E3', 'F34FBD4CB9207474', 'F36F4FC5A884C3DA', 'F37346E94569EA9E', 'F3A05C8D912975AB', 'F3C90449CB042238', 'F3E821DFEDE6BF03', 'F4124F43827BAA36', 'F52738C4461D426E', 'F540E917082B3F70', 'F561D76FF3C35292', 'F571D96ED3330551', 'F6785D85CBDA2BA6', 'F6C716472A556D8A', 'F71DD44DAB783CEB', 'F74B5B5FBD569D94', 'F76A4CA089865328', 'F7B9D8D3C0FC9881', 'F7BAFF2E0A04F11B', 'F8443EA4228113A4', 'F8564F547AFFC0B9', 'F89DE21913485212', 'F9945FBF588762BE', 'F9A5381028C80728', 'F9B6AD71F73676ED', 'FA8706D1E274805E', 'FABB6D49B11E673F', 'FB67C0C00EAF6628', 'FB75493CD55739BA', 'FB9FFB4BA8695C78', 'FC2DA0D7EF375B27', 'FC37CBE8211E02A8', 'FC67251B307846CB', 'FD11BEB17BD1E5F1', 'FD348F3ADE080914', 'FD83244D94C592DB', 'FDF035F9D4A1EB66', 'FE16209BD3C61223', 'FE578FBF32343FC3', 'FE9B25977A8537C2', 'FEE93E9CF4315356']
In [79]:
df_ab_group.query('user_id=="0240DB73D9B6F136"')
Out[79]:
user_id first_date region device event_dt event_name details date group ab_test day_count
15806 0240DB73D9B6F136 2020-12-10 EU PC 2020-12-10 07:23:24 login NaN 2020-12-10 B recommender_system_test 0.0
15807 0240DB73D9B6F136 2020-12-10 EU PC 2020-12-12 07:39:10 login NaN 2020-12-12 B recommender_system_test 2.0
In [80]:
len(single_event_users)
Out[80]:
695

В наших данных 695 пользователей не совершивших ни одного события, кроме login. Убираю их из анализа

In [81]:
df_ab_group = df_ab_group[~df_ab_group['user_id'].isin(single_event_users)]

Фильтрую датасет по количеству дней, не более 14

In [82]:
df_ab_group = df_ab_group.query('day_count<=13')
In [83]:
df_ab_group = df_ab_group.reset_index()
In [84]:
df_ab_group.groupby('group')['user_id'].agg('nunique')
Out[84]:
group
A    2120
B     666
Name: user_id, dtype: int64

После фильтрации по количеству дней, количество пользователей в группе А=2120 а в группе В=666

Cтроим гистограмма х возраст и у количество событий двойная для обоих групп

In [85]:
t_sleep_a = df_ab_group.query('group=="A"')
t_fresh_b = df_ab_group.query('group=="B"')
In [86]:
plt.subplots(figsize=(8, 4), dpi=100)
plt.hist(t_sleep_a['day_count'] , alpha=0.5, label="группа А")
plt.hist(t_fresh_b['day_count'],color='red', alpha=0.5, label="группа В")
plt.axvline(t_sleep_a['day_count'].mean(), color='blue', linestyle='dashed', linewidth=2, label='Mean')
plt.axvline(t_fresh_b['day_count'].mean(), color='red', linestyle='dashed', linewidth=2, label='Mean')
plt.legend(loc='upper right')
plt.title('Гистограмма распределения по количеству дней по группам')
plt.xlabel('Количество дней')
plt.ylabel('количество пользователей')
plt.show()

На гистограмме видно что среднее количество дней на совершение событий в обоих греппах примерно 3 дня. Но большинство событий происходит в первый день, например у группы В это 2000 пользоателей а у группы А 8000 пользователей

In [87]:
df_ab_mean_a = df_ab_group.query('group=="A"')
df_ab_mean_b = df_ab_group.query('group=="B"')
t_mean_a = df_ab_mean_a.groupby('user_id')['event_name'].count()
t_mean_b = df_ab_mean_b.groupby('user_id')['event_name'].count()
print('Cреднее количество событий на пользователя, группа А:',round(t_mean_a.mean(),2))
print('Cреднее количество событий на пользователя, группа В:',round(t_mean_b.mean(),2))
Cреднее количество событий на пользователя, группа А: 7.69
Cреднее количество событий на пользователя, группа В: 6.25

В среднем в группах пользователи совершали по 7,7 событый в группе А и по 6,25 в В

Оценка корректности проведения теста¶

Выделите пользователей участвующих в тесте и проверьте:¶

  • период набора пользователей в тест и его соответствие требованиям технического задания;
  • регион регистрации пользователей: все ли попавшие в тест пользователи представляют целевой регион и составляет ли общее количество пользователей из целевого региона 15% от общего числа пользователей из целевого региона, зарегистрированных в период набора пользователей в тест;
  • динамику набора пользователей в группы теста и проверьте равномерность распределения пользователей по группам теста и корректность их формирования;
  • (опционально) оцените недельную цикличность набора пользователей в группы

Сторою график набора пользователей

In [88]:
users_by_date_a = t_sleep_a.groupby('first_date')['user_id'].nunique()
users_by_date_b = t_fresh_b.groupby('first_date')['user_id'].nunique()

plt.subplots(figsize=(8, 4), dpi=100)
plt.bar(users_by_date_a.index, users_by_date_a.values , alpha=0.5, label="группа А")
plt.bar(users_by_date_b.index, users_by_date_b.values,color='red', alpha=0.5, label="группа В")
plt.legend(loc='upper right')
plt.title('Динамика набора пользователей по дням регистрации и группам')
plt.xlabel('Дата регистрации')
plt.ylabel('Количество пользователей')
plt.xticks(rotation=45) 
plt.show()

По гистограмме видно, что в первый день начала теста в группу В попало больше пользователей чем в группу А, через неделю был резкие скачок притока пользователей, особенно в группу А. Так как у нас пользователей в группе А гораздо больше чем в тестовой, это хорошо видно на графике. Распределение пользователей из нашего региона ранее определил ранее что 17 процентов учавствуют на нашем тесте

Посмотрим распределение по дням недели

In [89]:
df_ab_group['day_of_week'] = df_ab_group['first_date'].dt.day_name()
In [90]:
df_ab_group_a = df_ab_group.query('group=="A"')
df_ab_group_b = df_ab_group.query('group=="B"')
In [91]:
users_by_day_a = df_ab_group_a.groupby('day_of_week')['user_id'].nunique()
users_by_day_b = df_ab_group_b.groupby('day_of_week')['user_id'].nunique()

plt.subplots(figsize=(8, 4), dpi=100)
plt.bar(users_by_day_a.index, users_by_day_a.values , alpha=0.5, label="группа А")
plt.bar(users_by_day_b.index, users_by_day_b.values,color='red', alpha=0.5, label="группа В")
plt.legend(loc='upper right')
plt.title('Динамика набора пользователей по дням недели и группам')
plt.xlabel('День недели')
plt.ylabel('Количество пользователей')
plt.xticks(rotation=45) 
plt.show()

Чаще всего пользователи попадали в тест по понедельникам, что в группе А то и В. В остальные дни недели относительно равномерно

In [92]:
users_by_day_a = df_ab_group_a.groupby('device')['user_id'].nunique()
users_by_day_b = df_ab_group_b.groupby('device')['user_id'].nunique()

plt.subplots(figsize=(8, 4), dpi=100)
plt.bar(users_by_day_a.index, users_by_day_a.values , alpha=0.5, label="группа А")
plt.bar(users_by_day_b.index, users_by_day_b.values,color='red', alpha=0.5, label="группа В")
plt.legend(loc='upper right')
plt.title('Распределение  пользователей устройствам и группам')
plt.xlabel('Устройство')
plt.ylabel('Количество пользователей')
plt.xticks(rotation=45) 
plt.show()

Чаще всего пользователи в обоих группах заходили на платформу с Android и PC

Среднее количество событий на пользователя в группе

In [93]:
events_per_user_a = df_ab_group_a.groupby('user_id')['event_name'].count()
events_per_user_b = df_ab_group_b.groupby('user_id')['event_name'].count()
print(events_per_user_a.mean())
print(events_per_user_b.mean())
7.694811320754717
6.253753753753754

В среднем в группе А совершали 7,7 событий а в группе В 6,25 события

Изучите данные о пользовательской активности:¶

  • даты совершения событий участниками теста: совпадают ли они с датами проведения теста, согласно техническому заданию;
  • активность пользователей: все ли зарегистрированные пользователи прошли авторизацию и совершали переход по продуктовой воронке; если есть пользователи, которые не совершали событий после регистрации, изучите их количество и распределение между группамиn теста; сделайте вывод о необходимости учитывать пользователей без событий при изучении результатов теста;
  • горизонт анализа: рассчитайте лайфтайм совершения события пользователем после регистрации, оставьте только те события, которые были совершены в первые 14 дней с момента регистрации; проверьте, что все участники теста имели возможность совершать события все 14 дней с момента регистрации, оцените когда пользователи совершают свои первые события каждого вида.
  • Представьте развернутый вывод о соответствии теста требованиям технического задания и возможности получения достоверных результатов АБ-теста, исходя из базового показателя конверсии в 50%.

Смотрим даты совершения событий участникам теста

In [94]:
df_ab_group = df_ab_group.drop('index',axis=1)
In [95]:
df_ab_g = df_ab_group.pivot_table(index='date', columns=['event_name','group'], values='user_id', aggfunc='nunique')

# построение столбчатой диаграммы
fig, ax = plt.subplots(figsize=(25, 8))
df_ab_g.plot(kind='bar', ax=ax)
ax.set_xlabel('Дата')
ax.set_ylabel('Количество пользователей')
ax.set_title('Количество уникальных пользователей по событиям и группам')
plt.show()

Всплеск активности пользователей пришелся на 21/12. И это всплеск не связан ни с какими маркетинговыми событиями. Только одно мероприятие проходило в период и регионе проведения нашего теста. С 25/12 по 03/01 мероприятие "Christmas&New Year Promo"

In [96]:
users_day_a = df_ab_group_a.groupby('date')['event_name'].count()
users_day_b = df_ab_group_b.groupby('date')['event_name'].count()

plt.subplots(figsize=(8, 4), dpi=100)
plt.bar(users_day_a.index, users_day_a.values , alpha=0.5, label="группа А")
plt.bar(users_day_b.index, users_day_b.values,color='red', alpha=0.5, label="группа В")
plt.legend(loc='upper right')
plt.title('Общий график всех событий')
plt.xlabel('Дата')
plt.ylabel('Количество событий')
plt.xticks(rotation=45) 
plt.show()

Расмотрим отдельно по группам

In [97]:
df_pivot_a = df_ab_group_a.pivot_table(index='date', columns='event_name', values='user_id', aggfunc='nunique')
fig = px.bar(df_pivot_a, barmode='group')
fig.update_layout(title={
        'text': "Количество уникальных пользователей по датам и событиям в группе А",
        'y':0.95,
        'x':0.5,
        'xanchor': 'center',
        'yanchor': 'top'}, xaxis_title='Дата', yaxis_title='Количество пользователей')
fig.show()
Dec 92020Dec 12Dec 15Dec 18Dec 21Dec 24Dec 270100200300400500600700
event_nameloginproduct_cartproduct_pagepurchaseКоличество уникальных пользователей по датам и событиям в группе АДатаКоличество пользователей
plotly-logomark
In [98]:
df_pivot_b = df_ab_group_b.pivot_table(index='date', columns='event_name', values='user_id', aggfunc='nunique')
fig = px.bar(df_pivot_b, barmode='group')
fig.update_layout(title={
        'text': "Количество уникальных пользователей по датам и событиям в группе В",
        'y':0.95,
        'x':0.5,
        'xanchor': 'center',
        'yanchor': 'top'}, xaxis_title='Дата', yaxis_title='Количество пользователей')
fig.show()
Dec 92020Dec 12Dec 15Dec 18Dec 21Dec 24Dec 27020406080100120140
event_nameloginproduct_cartproduct_pagepurchaseКоличество уникальных пользователей по датам и событиям в группе ВДатаКоличество пользователей
plotly-logomark

В группе А резкий всплеск активности наблюдается с 14/12 и подьем продолжается до 21/12, после чего идет резкий спад, В группе В с превого дня относительная стабильность по событиям, но спад также наблюдается с 21/12. По этим данным можно сказать что общее поведение в обоих группах одинаковое Интересно что же так повлияло на активность пользователей. Стоит обратить внимание что есть существенная разница в показателях на оси У наших графиков, Количество пользователей в обоих группах не одинаковое

Ранее мы уже убрали из анализа пользователей которые не совершили ни одного действия и не прожили 14 дней

In [99]:
df_ab_d = df_ab_group.pivot_table(index='day_count', columns=['event_name','group'], values='user_id', aggfunc='nunique')

# построение столбчатой диаграммы
fig, ax = plt.subplots(figsize=(20, 8))
df_ab_d.plot(kind='bar', ax=ax)
ax.set_xlabel('День совершения действия с даты регистрвции')
ax.set_ylabel('Количество пользователей')
ax.set_title('Количество уникальных пользователей по Дням совершения события')
plt.show()

В обоих группах события весь спектр событий совершается в первый день. Посмотрим по группам отдельно

In [100]:
df_pivot_aa = df_ab_group_a.pivot_table(index='day_count', columns='event_name', values='user_id', aggfunc='nunique')
fig = px.bar(df_pivot_aa, barmode='group')
fig.update_layout(title={
        'text': "Количество уникальных пользователей по Cобытиям в группе А",
        'y':0.95,
        'x':0.5,
        'xanchor': 'center',
        'yanchor': 'top'}, xaxis_title='День после регистрации', yaxis_title='Количество пользователей')
fig.show()
0246810120500100015002000
event_nameloginproduct_cartproduct_pagepurchaseКоличество уникальных пользователей по Cобытиям в группе АДень после регистрацииКоличество пользователей
plotly-logomark
In [101]:
df_pivot_ab = df_ab_group_b.pivot_table(index='day_count', columns='event_name', values='user_id', aggfunc='nunique')
fig = px.bar(df_pivot_ab, barmode='group')
fig.update_layout(title={
        'text': "Количество уникальных пользователей по Cобытиям в группе В",
        'y':0.95,
        'x':0.5,
        'xanchor': 'center',
        'yanchor': 'top'}, xaxis_title='День после регистрации', yaxis_title='Количество пользователей')
fig.show()
0246810120100200300400500600
event_nameloginproduct_cartproduct_pagepurchaseКоличество уникальных пользователей по Cобытиям в группе ВДень после регистрацииКоличество пользователей
plotly-logomark

В обоих группах, большинство пользователей совершают все события в первый день после регистрации, И чаще всего минуют событие корзина, и сразу переходят на событие Оплата. Можно сказать что поведение в группах довольно одинаковое

Убедитесь, что время проведения теста не совпадает с маркетинговыми и другими активностями.

In [102]:
# выбираем период времени, в котором проводился тест
test_start = df_ab_group['date'].min()
test_end = df_ab_group['date'].max()

# выбираем период времени, в котором проводились маркетинговые события
marketing_start = df_ab['start_dt'].min()
marketing_end = df_ab['finish_dt'].max()

# проверяем пересечение периодов времени
if (test_start >= marketing_start and test_start <= marketing_end) or (test_end >= marketing_start and test_end <= marketing_end):
    print('Внимание! Время проведения теста пересекается с маркетинговыми активностями.')
else:
    print('Время проведения теста не пересекается с маркетинговыми активностями.')

# выводим список событий, которые пересекаются с маркетинговыми активностями
intersect_events = df_ab_group[df_ab_group['event_dt'].between(marketing_start, marketing_end)]['event_name'].unique()
if intersect_events.size > 0:
    print('Следующие события пересекаются со временем проведения маркетинговых активностей:')
    print(intersect_events)
else:
    print('Нет событий, пересекающихся со временем проведения маркетинговых активностей.')
Внимание! Время проведения теста пересекается с маркетинговыми активностями.
Следующие события пересекаются со временем проведения маркетинговых активностей:
['product_page' 'login' 'purchase' 'product_cart']
In [103]:
intersect_marketing = df_ab[(df_ab['start_dt'] <= test_end) & (df_ab['finish_dt'] >= test_start)]['event_name'].unique()

if intersect_marketing.size > 0:
    print('Тест пересекается с маркетинговыми активностями:')
    for marketing in intersect_marketing:
        marketing_dates = df_ab[df_ab['event_name'] == marketing][['start_dt', 'finish_dt']].values
        print(f'Маркетинговое событие "{marketing}" проводилось в период с {marketing_dates[0][0]} по {marketing_dates[0][1]}.')
else:
    print('Тест не пересекается с маркетинговыми активностями.')
Тест пересекается с маркетинговыми активностями:
Маркетинговое событие "Christmas&New Year Promo" проводилось в период с 2020-12-25T00:00:00.000000000 по 2021-01-03T00:00:00.000000000.

В очередной раз подтвердили данные что толоко одно маркетинговое событе происходило в дни проведения теста, это Маркетинговое событие "Christmas&New Year Promo" проводилось в период с 2020-12-25 по 2021-01-03. И оно ни как не могло повлиять на активность пользователей в обоих группах

Продуктовая воронка: постройте простые продуктовые воронки для двух групп теста с учетом логической последовательности совершения событий; изучите изменение конверсии в продуктовой воронке тестовой группы, по сравнению с контрольной: наблюдается ли ожидаемый эффект увеличения конверсии в группе В, относительно конверсии в группе А? Сделайте общий вывод об изменении пользовательской активности в тестовой группе, по сравнению с контрольной.

In [104]:
user_uniq_count_a =  df_ab_group_a['user_id'].nunique()
user_uniq_count_b =  df_ab_group_b['user_id'].nunique()
In [105]:
tt = df_ab_group.query('event_name=="login"')
tt['user_id'].nunique()
Out[105]:
2785
In [106]:
users_without_login = df_ab_group[~df_ab_group['user_id'].isin(df_ab_group[df_ab_group['event_name'] == 'login']['user_id'])]['user_id'].unique()

# выведем список таких пользователей
print('Пользователи без события login:')
print(users_without_login)
Пользователи без события login:
['5FF8B6AB257B404F']
In [107]:
df_ab_group.query('user_id=="5FF8B6AB257B404F"')
Out[107]:
user_id first_date region device event_dt event_name details date group ab_test day_count day_of_week
1573 5FF8B6AB257B404F 2020-12-07 EU Android 2020-12-07 04:17:47 purchase 4.99 2020-12-07 B recommender_system_test 0.0 Monday

Случайно обнаружился пользователь без Логина даже. Странно. Но в целом понятно,что по событию логин строить воронку не стоит, конверсия 100%

In [108]:
ttpp_a = df_ab_group.query('event_name=="product_page" and group=="A"')
user_count_pp_a = round(ttpp_a['user_id'].nunique()/user_uniq_count_a*100,2)
ttpp_b = df_ab_group.query('event_name=="product_page" and group=="B"')
user_count_pp_b = round(ttpp_b['user_id'].nunique()/user_uniq_count_b*100,2)
ttpc_a = df_ab_group.query('event_name=="product_cart" and group=="A"')
user_count_pc_a = round(ttpc_a['user_id'].nunique()/user_uniq_count_a*100,2)
ttpc_b = df_ab_group.query('event_name=="product_cart" and group=="B"')
user_count_pc_b = round(ttpc_b['user_id'].nunique()/user_uniq_count_b*100,2)
ttpu_a = df_ab_group.query('event_name=="purchase" and group=="A"')
user_count_pu_a = round(ttpu_a['user_id'].nunique()/user_uniq_count_a*100,2)
ttpu_b = df_ab_group.query('event_name=="purchase" and group=="B"')
user_count_pu_b = round(ttpu_b['user_id'].nunique()/user_uniq_count_b*100,2)
In [109]:
stages = ["product_page", "product_cart", "purchase"]
df_mtl = pd.DataFrame(dict(number=[user_count_pp_a, user_count_pc_a, user_count_pu_a], stage=stages))
df_mtl['group'] = 'A'
df_tor = pd.DataFrame(dict(number=[user_count_pp_b, user_count_pc_b, user_count_pu_b], stage=stages))
df_tor['group'] = 'B'
df = pd.concat([df_mtl, df_tor], axis=0)
fig = px.funnel(df, x='number', y='stage', color='group')
fig.show()
79.4836.8939.2974.0236.6437.39purchaseproduct_cartproduct_page
groupABstage
plotly-logomark

На графике конверсии видно что по группам и на каждом шаге они существенных различий нет, например конверсия у первого события "Переход на страницу с товаром" у группы А 80% а у группы В только 74%. а до оплаты у группы А доходит 39% у В 37. Судя по этой воронке можно уверенно сказать что изменения проводимые с тестовой (В) группой не принесли успешного результата

In [110]:
ttlo_a = df_ab_group.query('event_name=="login" and group=="A"')
user_count_lo_a = round(ttlo_a['user_id'].nunique()/user_uniq_count_a*100,2)
ttlo_b = df_ab_group.query('event_name=="login" and group=="B"')
user_count_lo_b = round(ttlo_b['user_id'].nunique()/user_uniq_count_b*100,2)
ttpp_a = df_ab_group.query('event_name=="product_page" and group=="A"')
user_count_pp_a = round(ttpp_a['user_id'].nunique()/user_uniq_count_a*100,2)
ttpp_b = df_ab_group.query('event_name=="product_page" and group=="B"')
user_count_pp_b = round(ttpp_b['user_id'].nunique()/user_uniq_count_b*100,2)
ttpc_a = df_ab_group.query('event_name=="product_cart" and group=="A"')
user_count_pc_a = round(ttpc_a['user_id'].nunique()/user_uniq_count_a*100,2)
ttpc_b = df_ab_group.query('event_name=="product_cart" and group=="B"')
user_count_pc_b = round(ttpc_b['user_id'].nunique()/user_uniq_count_b*100,2)
ttpu_a = df_ab_group.query('event_name=="purchase" and group=="A"')
user_count_pu_a = round(ttpu_a['user_id'].nunique()/user_uniq_count_a*100,2)
ttpu_b = df_ab_group.query('event_name=="purchase" and group=="B"')
user_count_pu_b = round(ttpu_b['user_id'].nunique()/user_uniq_count_b*100,2)
In [111]:
stages = ["login","product_page", "product_cart", "purchase"]
df_mtl = pd.DataFrame(dict(number=[user_count_lo_a, user_count_pp_a, user_count_pc_a, user_count_pu_a], stage=stages))
df_mtl['group'] = 'A'
df_tor = pd.DataFrame(dict(number=[user_count_lo_b, user_count_pp_b, user_count_pc_b, user_count_pu_b], stage=stages))
df_tor['group'] = 'B'
df = pd.concat([df_mtl, df_tor], axis=0)
fig = px.funnel(df, x='number', y='stage', color='group')
fig.show()
10079.4836.8939.2999.8574.0236.6437.39purchaseproduct_cartproduct_pagelogin
groupABstage
plotly-logomark

Проведите оценку результатов A/B-тестирования:¶

  • Проверьте статистическую разницу долей z-критерием.
  • Что можно сказать про результаты A/B-тестирования? Был ли достигнут ожидаемый эффект в изменении конверсии?
In [112]:
user_c_pp_a = ttpp_a['user_id'].nunique()
user_c_pp_b = ttpp_b['user_id'].nunique()
user_c_pc_a = ttpc_a['user_id'].nunique()
user_c_pc_b = ttpc_b['user_id'].nunique()
user_c_pu_a = ttpu_a['user_id'].nunique()
user_c_pu_b = ttpu_b['user_id'].nunique()

Формулирую гипотезы, по событиям:"product_page", "product_cart", "purchase" между группами А и В:

Н0: В сравниваемых группах нет статистически значимой разницы

Н1: В сравниваемых группах есть статистически значимая разница

In [113]:
alpha = 0.05
successes = [user_c_pp_a, user_c_pp_b]
trials = [user_uniq_count_a, user_uniq_count_b]
stat, p_value = proportions_ztest(successes, trials)
print('\nШаг 1: MainScreenAppear')
print('p-значение:', p_value)
if p_value < alpha:
    print('Отвергаем нулевую гипотезу: различие конверсий на шаге 1 статистически значимо')
else:
    print('Не получилось отвергнуть нулевую гипотезу: различие конверсий на шаге 1 не статистически значимо')
Шаг 1: MainScreenAppear
p-значение: 0.0029370596204435742
Отвергаем нулевую гипотезу: различие конверсий на шаге 1 статистически значимо
In [114]:
def check_conversion_significance(user_uniq_count_a, user_uniq_count_b, user_c_pp_a, user_c_pp_b, user_c_pc_a, user_c_pc_b, user_c_pu_a ,user_c_pu_b):
    
    alpha = 0.14
    
   
    successes = [user_c_pp_a, user_c_pp_b]
    trials = [user_uniq_count_a, user_uniq_count_b]
    stat, p_value = proportions_ztest(successes, trials)
    print('\nШаг 1: product_page')
    print('p-значение:', p_value)
    if p_value < alpha:
        print('Отвергаем нулевую гипотезу: различие конверсий на шаге 1 статистически значимо')
    else:
        print('Не получилось отвергнуть нулевую гипотезу: различие конверсий на шаге 1 не статистически значимо')


    successes = [user_c_pc_a, user_c_pc_b]
    trials = [user_uniq_count_a, user_uniq_count_b]
    stat, p_value = proportions_ztest(successes, trials)
    print('\nШаг 2: product_cart')
    print('p-значение:', p_value)
    if p_value < alpha:
        print('Отвергаем нулевую гипотезу: различие конверсий на шаге 2 статистически значимо')
    else:
        print('Не получилось отвергнуть нулевую гипотезу: различие конверсий на шаге 2 не статистически значимо')


    successes = [user_c_pu_a, user_c_pu_b]
    trials = [user_uniq_count_a, user_uniq_count_b]
    stat, p_value = proportions_ztest(successes, trials)
    print('\nШаг 3: purchase')
    print('p-значение:', p_value)
    if p_value < alpha:
        print('Отвергаем нулевую гипотезу: различие конверсий на шаге 3 статистически значимо')
    else:
        print('Не получилось отвергнуть нулевую гипотезу: различие конверсий на шаге 3 не статистически значимо')
In [115]:
check_conversion_significance(user_uniq_count_a, user_uniq_count_b, user_c_pp_a, user_c_pp_b, user_c_pc_a, user_c_pc_b, user_c_pu_a ,user_c_pu_b)
Шаг 1: product_page
p-значение: 0.0029370596204435742
Отвергаем нулевую гипотезу: различие конверсий на шаге 1 статистически значимо

Шаг 2: product_cart
p-значение: 0.9070540547116017
Не получилось отвергнуть нулевую гипотезу: различие конверсий на шаге 2 не статистически значимо

Шаг 3: purchase
p-значение: 0.37888740748690086
Не получилось отвергнуть нулевую гипотезу: различие конверсий на шаге 3 не статистически значимо

При проверке статистически значимой разницы выборки, только на событии "product_page" не подтвердил гипотезу что доли в выборке равны, на остальных этапах "Корзина" и "Покупка", доли в выборка статистических различий не имеют. Можно сказать, что изменения, внесенные на этапе представления товара, оказали влияние на поведение пользователей, в то время как изменения на этапах корзины и покупки - нет. В целом тест можно считать не удачным, различия только на одном событии. А задача теста увеличить конверсию на всех не менее 5%. По конверсии мы видели что в абсолютных показателях результат также не достигнут

Oбщий Вывод:¶

Во время выполнения проекта были проведены следеющие этапы работ:

  • Загрузка данных
  • Предобработка. Проверка на пропуски и дубликаты. Изменения типа данных. Замена названий.
  • Прведена проверка на соответствие Техническому заданию. Сделаны следующие заключения:

    • 1602 Пользователя одновременно входят и в наш тест и в конкурирующий. В нашем тесте(recommender_system_test) таких нет. То есть пользователь входит и в разные группы но и в разные тесты. И таких 23,2% от общего числа, участников теста. Удалить такое количество не получиться
    • Пересечение пользователей по тестовой группе 688 пользователей. Около 24% пользователей из группы А конкурирующего теста входят в группу А нашего теста, и 23.5% по группе В
    • Процент попавших в обе группы достаточно равномерный, поэтому и воздействие на группы оказывает равномерное - Только одно мероприятие проходило в период и регионе проведения нашего теста. С 25/12 по 03/01 - Отфильтровал пользователей по дате и региону, осталось 42340 новых пользователей. Данные по пользователям соответствую нашему ТЗ набирались с 07-12 по 21-12
    • Данные в тесте только с 07-12 по 30-12, учитывая что наш тест продолжался до 04-01 то данных за 5 дней не достает.Восстановить данные не возможно. Пользователей 58703. - Создали таблицу с новыми пользователями и событиями которые он совершили, исходя из тех. задания. Таблицу с участниками нашего теста мы проверяли ранее. Наш тест recommender_system_test есть в этой таблице и также есть наши группы А и В - Только около 17.0 % новых пользователей участвовало в нашем тесте, от общего числа зарегестрировыных в период проведения теста и регион EU
    • В наших данных 695 пользователей не совершивших ни одного события, кроме login. Убираю их из анализа - После фильтрации по количеству дней, количество пользователей в группе А=2120 а в группе В=666 - На гистограмме видно что среднее количество дней на совершение событий в обоих греппах примерно 3 дня. Но большинство событий происходит в первый день, например у группы В это 2000 пользоателей а у группы А 8000 пользователей
  • При анализе результатов теста получил следующие выводы:
  1. По гистограмме видно, что в первый день начала теста в группу В попало больше пользователей чем в группу А, через неделю был резкие скачок притока пользователей, особенно в группу А. Так как у нас пользователей в группе А гораздо больше чем в тестовой, это хорошо видно на графике. Распределение пользователей из нашего региона ранее определил ранее что 15.8 процентов учавствуют на нашем тесте
  2. Чаще всего пользователи попадали в тест по понедельникам, что в группе А то и В. В остальные дни недели относительно равномерно. В среднем в группах пользователи совершали по 7,7 событый в группе А и по 6,25 в В
  3. Чаще всего пользователи в обоих группах заходили на платформу с Android и PC
  4. Всплеск активности пользователей пришелся на 21/12. И это всплеск не связан ни с какими маркетинговыми событиями. Только одно мероприятие проходило в период и регионе проведения нашего теста. С 25/12 по 03/01 мероприятие "Christmas&New Year Promo"
  5. В группе А резкий всплеск активности наблюдается с 14/12 и подьем продолжается до 21/12, после чего идет резкий спад, В группе В с превого дня относительная стабильность по событиям, но спад также наблюдается с 21/12. По этим данным можно сказать что общее поведение в обоих группах одинаковое Интересно что же так повлияло на активность пользователей. Стоит обратить внимание что есть существенная разница в показателях на оси У наших графиков, Количество пользователей в обоих группах не одинаковое
  6. В обоих группах, большинство пользователей совершают все события в первый день после регистрации, И чаще всего минуют событие корзина, и сразу переходят на событие Оплата. Можно сказать что поведение в группах довольно одинаковое
  • При построении Продуктовой воронки определил: На графике конверсии видно что по группам и на каждом шаге они существенных различий нет, например конверсия у первого события "Переход на страницу с товаром" у группы А 80% а у группы В только 74%. а до оплаты у группы А доходит 39% у В 37. Судя по этой воронке можно уверенно сказать что изменения проводимые с тестовой (В) группой не принесли успешного результата
  • При проверке статистически значимой разницы выборки, только на событии "product_page" не подтвердил гипотезу что доли в выборке равны, на остальных этапах "Корзина" и "Покупка", доли в выборка статистических различий не имеют. Можно сказать, что изменения, внесенные на этапе представления товара, оказали влияние на поведение пользователей, в то время как изменения на этапах корзины и покупки - нет. В целом тест можно считать не удачным, различия только на одном событии. А задача теста увеличить конверсию на всех не менее 5%. По конверсии мы видели что в абсолютных показателях результат также не достигнут